package com.xyz.shiro.jwt;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.free.ext.logger.FreeLogger;
import com.free.ext.logger.FreeLoggerFactory;
import com.xyz.shiro.exception.JwtException;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.Map;
import java.util.Properties;

public class JwtTools {

    /**
     * 过期时间改为从配置文件获取
     */
    private Integer accessTokenExpireTime;

    /**
     * JWT认证加密私钥(Base64加密)
     */
    private String encryptJWTKey;

    /**
     * Token刷新时间
     */
    private Integer refreshTokenTime;

    public static final String ACCESS_TOKEN_EXPIRE_TIME = "Access_Token_Expire_Time";

    public static final String ENCRYPT_JWT_KEY = "Encrypt_Jwt_Key";

    public static final String ACCESS_TOKEN_REFRESH_TIME = "Access_Token_Refresh_Time";

    public static final String JWT_ACCOUNT = "JWTACCOUNT";

    public static final String DEVICE_TYPE = "DEVICETYPE";

    private static final FreeLogger LOGGER = FreeLoggerFactory.getLogger(JwtTools.class);

    JwtTools(){
    }

    public static JwtTools getInstance() {
        return JwtToolsContextHolder.instance;
    }

    public void loadConfig(String fileName) throws Exception{
        InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName);
        Properties properties = new Properties();
        try {
            properties.load(stream);
        } catch (IOException e) {
            String excepMsg = String.format("PropertiesUtils 处理文件：[%s]时发生异常", fileName);
            throw e;
        }
        this.accessTokenExpireTime = Integer.parseInt(properties.getProperty(ACCESS_TOKEN_EXPIRE_TIME));
        this.encryptJWTKey = properties.getProperty(ENCRYPT_JWT_KEY);
        this.refreshTokenTime =  Integer.parseInt(properties.getProperty(ACCESS_TOKEN_REFRESH_TIME));
    }

    /**
     * 校验token是否正确
     * @param token Token
     * @return boolean 是否正确
     *
     */
    public boolean verify(String token) {
        try {
            // 帐号加JWT私钥解密
            String secret = getClaim(token, JWT_ACCOUNT) + Base64ConvertUtil.encode(encryptJWTKey);
            Algorithm algorithm = Algorithm.HMAC256(secret);
            JWTVerifier verifier = JWT.require(algorithm)
                    .build();
            DecodedJWT jwt = verifier.verify(token);
            return true;
        } catch (Exception e) {
            LOGGER.error("JWTToken认证解密出现UnsupportedEncodingException异常:" + e.getMessage());
            throw new JwtException(JwtException.EnumJwtExceptionType.UnSupportEncoding,"不支持的编码",e);
        }
    }

    /**
     * 获得Token中的信息无需secret解密也能获得
     * @param token
     * @param claim
     *
     */
    public String getClaim(String token, String claim) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            // 只能输出String类型，如果是其他类型返回null
            return jwt.getClaim(claim).asString();
        } catch (JWTDecodeException e) {
            LOGGER.error("解密Token中的公共信息出现JWTDecodeException异常:" + e.getMessage());
            throw new JwtException(JwtException.EnumJwtExceptionType.DecodeFail, "解密失败",e);
        }
    }

    /**
     * 生成签名,expireTime后过期
     * @param account
     * @return
     */
    public String sign(String account, String deviceType, Integer tokenExprieTime) {
        try {
            // 帐号加JWT私钥加密
            String secret = account + Base64ConvertUtil.encode(encryptJWTKey);
            // 此处过期时间是以毫秒为单位，所以乘以1000
            Date now = new Date();
            Date date = new Date(now.getTime() + tokenExprieTime * 1000);
            Algorithm algorithm = Algorithm.HMAC256(secret);
            // 附带account帐号信息
            return JWT.create()
                    .withClaim(JWT_ACCOUNT, account)
                    .withClaim(DEVICE_TYPE, deviceType)
                    .withIssuedAt(now)
                    .withExpiresAt(date)
                    .sign(algorithm);
        } catch (UnsupportedEncodingException e) {
            LOGGER.error("JWTToken加密出现UnsupportedEncodingException异常:" + e.getMessage());
            throw new JwtException(JwtException.EnumJwtExceptionType.UnSupportEncoding,"不支持的编码",e);
        }
    }

    /**
     * 获得token中的信息无需secret解密也能获得
     * @return token中包含的签发时间
     */
    public Date getIssuedAt(String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getIssuedAt();
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    /**
     * 判断Token 是否过期
     * @param token
     * @return
     */
    public boolean isTokenExpire(String token, int exprieTime){
        LocalDateTime issueTime = LocalDateTime.ofInstant(getIssuedAt(token).toInstant(), ZoneId.systemDefault());
        return LocalDateTime.now().minusSeconds(exprieTime).isAfter(issueTime);
    }


    public Integer getAccessTokenExpireTime() {
        return accessTokenExpireTime;
    }

    public void setAccessTokenExpireTime(Integer accessTokenExpireTime) {
        this.accessTokenExpireTime = accessTokenExpireTime;
    }

    public String getEncryptJWTKey() {
        return encryptJWTKey;
    }

    public void setEncryptJWTKey(String encryptJWTKey) {
        this.encryptJWTKey = encryptJWTKey;
    }

    public Integer getRefreshTokenTime() {
        return refreshTokenTime;
    }

    public void setRefreshTokenTime(Integer refreshTokenTime) {
        this.refreshTokenTime = refreshTokenTime;
    }

    static class JwtToolsContextHolder {
        public static final JwtTools instance = new JwtTools();
    }

}
